Previous Book Contents Book Index Next

Inside Macintosh: /
QuickDraw 3D RAVE


Writing a Drawing Engine

This section shows how to write a new drawing engine and add it to the QuickDraw 3D Acceleration Layer.

IMPORTANT
You need to read this section only if you are developing custom 3D acceleration hardware or software. If you simply want to create a draw context and draw into it using low-level drawing functions, see "Using QuickDraw 3D RAVE," beginning on page 1-13.
To develop a new drawing engine and add it to the QuickDraw 3D Acceleration Layer, you need to perform these seven steps:

  1. Write methods for the public functions pointed to by the fields of a draw context structure (for example, setInt). These methods are described in detail in the section "Public Draw Context Methods," beginning on page 1-115.
  2. Write methods for the TQADrawPrivateNew and TQADrawPrivateDelete function prototypes. These functions are called internally by the QADrawContextNew and QADrawContextDelete functions, respectively. You use these methods to allocate and release any private data (such as state variables) maintained by your drawing engine. These methods are described in detail in the section "Private Draw Context Methods," beginning on page 1-136.
  3. Write methods for any texture and bitmap functions supported by your drawing engine (TQATextureNew, TQATextureDetach, TQATextureDelete, TQABitmapNew, TQABitmapDetach, and TQABitmapDelete). These functions are called by their public counterparts (for example, QABitmapNew). These methods are described in detail in the section "Texture and Bitmap Methods," beginning on page 1-141.
  4. Write a method to handle the QAEngineGestalt function when your drawing engine is the target engine. This method is described in detail on (page 1-138).
  5. Write a method to handle the QAEngineCheckDevice function when your drawing engine is the target engine. QuickDraw 3D RAVE calls this method to determine which devices your drawing engine supports. This method is described in detail on (page 1-138).
  6. Write a method for the TQAEngineGetMethod function prototype. QuickDraw 3D RAVE calls this method to get some of your engine's methods during engine registration. This method is described in detail on (page 1-147).
  7. Build your code as a shared library. The initialization routine of the shared library should register your drawing engine with QuickDraw 3D RAVE by calling the QARegisterEngine function.

The following sections describe some of these steps in more detail. The section "Supporting OpenGL Hardware," beginning on page 1-30 contains information that is useful if you are implementing a drawing engine to support hardware that is based on an OpenGL rasterization model.

Writing Public Draw Context Methods

As you've seen, the draw context structure (of type TQADrawContext) contains function pointers to the public draw context methods supported by your drawing engine. These methods are called whenever an application calls one of the public functions provided by QuickDraw 3D RAVE. For example, when an application calls the QADrawPoint function for a draw context associated with your drawing engine, your engine's TQADrawPoint method (pointed to by the drawPoint field) is called. The TQADrawPoint method is declared like this:

typedef void (*TQADrawPoint) (
const TQADrawContext *drawContext, 
const TQAVGouraud *v);
A draw context structure is passed as the first parameter to all the public draw context methods you need to define. This allows your methods to find the private data associated with the draw context (which is pointed to by the drawPrivate field).

Notice that the function prototype for a point-drawing method passes the draw context as a const parameter. This indicates that your method should not alter any of the fields of the draw context structure passed to it. Only three draw context methods (namely TQASetInt, TQASetFloat, and TQASetPtr) are allowed to alter the draw context.

Listing 1-7 shows a sample definition for a point-drawing method.

Listing 1-7 A TQADrawPoint method

void MyDrawPoint (const TQADrawContext *drawContext, const TQAVGouraud *v)
{
   MyPrivateData        *myData; /*our actual private data type*/

   /*Cast generic drawPrivate pointer to our actual private data type.*/
   myData = (MyPrivateData *) drawContext->drawPrivate;
   
   /*Call our z-buffered pixel drawing function with xyz and argb, and
     also pass it the current zfunction, which is stored in the private draw 
     context data structure. Note that this isn't a complete implementation!
     (We should be using kQATag_Width, for example.)*/

   MyDrawPixelWithZ(v->x, v->y, v->z, v->a, v->r, v->g, v->b,
                              myData->stateVariable[kQATag_ZFunction]);
}
Note
See "Public Draw Context Methods," beginning on page 1-115 for complete information on the public draw context methods your drawing engine must define.
Once you've defined the necessary public draw context methods, you need to insert pointers to those methods into a draw context structure. You accomplish this step in your TQADrawPrivateNew method, described in the next section.

Writing Private Draw Context Methods

Once you've written the public draw context methods supported by your drawing engine, you need to write several private draw context methods. In particular, you need to write a TQADrawPrivateNew method to initialize a draw context and a TQADrawPrivateDelete method to delete a draw context. The TQADrawPrivateNew method is called whenever an application creates a new draw context by calling the QADrawContextNew function. Listing 1-8 illustrates a sample TQADrawPrivateNew method.

Listing 1-8 A TQADrawPrivateNew method

TQAError MyDrawPrivateNew (
   TQADrawContext *drawContext,
   const TQADevice*device,
   const TQARect  *rect,
   const TQAClip  *clip,
   unsigned long  flags)
{
   MyPrivateData  *myData;

   /*Allocate a new MyPrivateData structure and store it in draw context.*/
   myData = MyDataNew(...);
   drawContext->drawPrivate = (TQADrawPrivate *) myData;
   if (!myData) 
      return (kQAOutOfMemory);

   /*Set the method pointers of drawContext to point to our draw methods.*/
   newDrawContext->setFloat = MySetFloat;
   newDrawContext->setInt = MySetInt;
   ...
   return(kQANoErr);
}
As you can see, the MyDrawPrivateNew function defined in Listing 1-8 allocates space for its private data, installs a pointer to that data in the drawPrivate field of the draw context structure, and then installs pointers to all the public draw context methods supported by the drawing engine into the draw context structure.

Your TQADrawPrivateDelete method should simply undo any work done by your TQADrawPrivateNew method. In this case, the delete method just needs to release the private storage allocated by the TQADrawPrivateNew method. Listing 1-9 shows a sample TQADrawPrivateDelete method.

Listing 1-9 A TQADrawPrivateDelete method

void MyDrawPrivateDelete (TQADrawPrivate *drawPrivate)
{
   MyDataDelete((MyPrivateData *) drawPrivate);
}
You register your private draw context methods with QuickDraw 3D RAVE using another private method, the TQAEngineGetMethod method. See "Registering a Drawing Engine," beginning on page 1-29 for details.

Handling Gestalt Selectors

To support calls to the public function QAEngineGestalt, your drawing engine must define a TQAEngineGestalt method. This method returns information about the capabilities of your drawing engine. For example, suppose that your drawing engine supports texture mapping and accelerates both Gouraud shading and line drawing. Suppose further that you have been assigned a vendor ID of 5, and that the engine ID of your engine is 1001. In that case, you could define a method like the one shown in Listing 1-10.

Listing 1-10 A TQAEngineGestalt method

TQAError MyEngineGestalt (TQAGestaltSelector selector, void *response)
{
   const static char *myEngineName = "SurfDraw 3D";

   switch (selector) {
      case kQAGestalt_OptionalFeatures:
         *((unsigned long *) response) = kQAOptional_Texture;
         break;
      case kQAGestalt_FastFeatures:
         *((unsigned long *) response) = kQAFast_Line | kQAFast_Gouraud;
         break;
      case kQAGestalt_VendorID:
         *((long *) response) = 5;
         break;
      case kQAGestalt_EngineID:
         *((long *) response) = 1001;
         break;
      case kQAGestalt_Revision:
         *((long *) response) = 0;
         break;
      case kQAGestalt_ASCIINameLength:
         *((long *) response) = strlen(myEngineName);
         break;
      case kQAGestalt_ASCIIName:
         strcpy(response, myEngineName);
         break;
      default:                /*must flag unrecognized selectors*/
         return (kQAParamErr);
   }
   return (kQANoErr);
}
If two different drawing engines should return identical vendor and engine IDs, QuickDraw 3D RAVE chooses the one that returns the most recent revision number (that is, the value returned for the kQAGestalt_Revision selector). The larger number is considered newer.

You register your TQAEngineGestalt method with QuickDraw 3D RAVE using the TQAEngineGetMethod method, described in the next section.

Registering a Drawing Engine

Once you written all the necessary public and private draw context methods, as well as methods to handle textures and bitmaps, you must write a TQAEngineGetMethod method that reports the addresses of some of those methods to QuickDraw 3D RAVE. Listing 1-11 shows a sample TQAEngineGetMethod method. Notice that this method returns the addresses only of the private draw context methods and the methods to handle textures and bitmaps. The pointers for the public draw context methods are assigned directly to the fields of a draw context structure by your TQADrawPrivateNew method (as shown in Listing 1-8).

Listing 1-11 A TQAEngineGetMethod method

TQAError MyEngineGetMethod (TQAEngineMethodTag methodTag, TQAEngineMethod *method)
{
   switch (methodTag) {
      case kQADrawPrivateNew:
         method->drawPrivateNew = MyDrawPrivateNew;
         break;
      case kQADrawPrivateDelete:
         method->drawPrivateDelete = MyDrawPrivateDelete;
         break;
      case kQAEngineCheckDevice:
         method->engineCheckDevice = MyEngineCheckDevice;
         break;
      case kQAEngineGestalt:
         method->engineGestalt = MyEngineGestalt;
         break;
      case kQABitmapNew:
         method->bitmapNew = MyBitmapNew;
         break;
      case kQABitmapDetach:
         method->bitmapDetach = MyBitmapDetach;
         break;
      case kQABitmapDelete:
         method->bitmapDelete = MyBitmapDelete;
         break;
      default:
         return(kQANotSupported);
   }
   return(kQANoErr);
}
Finally, you register your drawing engine by passing the address of your TQAEngineGetMethod method to the QARegisterEngine function:

QARegisterEngine(&MyEngineGetMethod);
You can call QARegisterEngine in two ways. During product development, you can link your drawing engine code directly with a test application, in which case you should call QARegisterEngine from your application's initialization code. Alternatively, once you've completed development, you should build your engine's code into a shared library of type 'tnsl'. In this case, you should call QARegisterEngine from the initialization routine of the shared library. When the shared library containing QuickDraw 3D RAVE is loaded, it searches for and loads any drawing engines contained in shared libraries in the current folder or in the Extensions folder.

Supporting OpenGL Hardware

This section contains information that is useful if you are implementing a drawing engine to support hardware that is based on an OpenGL rasterization model. It describes special considerations for handling transparency and texture mapping.

Transparency

QuickDraw 3D RAVE supports three transparency models: the premultiplied, interpolated, and OpenGL transparency models. Support for the OpenGL transparency model (indicated by the kQABlend_OpenGL constant) should be automatic for hardware that is based on the OpenGL rasterization model. The other two models, indicated by the kQABlend_PreMultiply and kQABlend_Interpolate constants) may require emulation by your drawing engine.

For example, consider the premultiplied blending function, specified by these equations: a = 1+-((1+-INDEXES (not implemented))×(1+-INDEXES (not implemented))) r = INDEXES (not implemented)+((1+-INDEXES (not implemented))×INDEXES (not implemented)) g = INDEXES (not implemented)+((1+-INDEXES (not implemented))×INDEXES (not implemented)) b = INDEXES (not implemented)+((1+-INDEXES (not implemented))×INDEXES (not implemented))

(Here, the factors as, rs, gs, and bs represent the alpha, red, green and blue components of a source pixel; the factors ad, rd, gd, and bd represent the alpha, red, green and blue components of a destination pixel.)

Note
A complete description of how transparent objects are blended together with each of these models is provided in "Blending Operations" (page 1-49).
OpenGL directly supports the premultiplied transparency blending function (and the interpolated transparency blending function) for the RGB components only. In other words, the alpha channel component (which is the same for both blending operations) cannot be directly implemented in OpenGL-compliant hardware. It is possible, however, to emulate these two transparency modes on OpenGL hardware, using several different methods. You can blend the RGB values only, or you can blend the ARGB values using a multipass algorithm. Which of these emulations you use depends on whether your drawing engine is associated with a frame buffer that stores an alpha channel or not.

If your drawing engine is associated with a frame buffer that doesn't store an alpha channel value, you can implement the premultiplied and interpolated blending functions by simply ignoring the alpha channel component. These functions are then equivalent to OpenGL blending modes. The premulitplied blending function, with its alpha channel ignored, can be emulated by this function:

gBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
Similarly, the interpolated blending function, with its alpha channel ignored, can be emulated by this function:

gBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
IMPORTANT
A drawing engine that uses this method of emulating the QuickDraw 3D RAVE blending functions on OpenGL hardware should not set the kQAOptional_BlendAlpha flag of the kQAGestalt_OptionalFeatures selector to the QAEngineGestalt function.
To achieve a more complete blending, you can have your drawing engine rasterize each transparent object more than once, altering in each pass the blending mode, object alpha channel, and buffer write masks. The first pass should perform RGB blending. Accordingly, you should disable writing any alpha channel or z buffer data during this pass.

/*first pass*/
glColorMask(TRUE, TRUE, TRUE, FALSE);/*disable alpha channel*/
glDepthMask(FALSE);                 /*disable Z buffer*/
if (premultpliedTransparency)
   glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
else
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
/*render the object here*/
On the second pass, you should set the frame buffer alpha channel value to (1-as)(1-ad). To do this, you need to render the object again, with a different alpha value, as follows:

/*second pass*/
glColorMask(FALSE, FALSE, FALSE, TRUE);/*enable alpha channel*/
glDepthMask(FALSE);                 /*disable Z buffer*/
glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ZERO);
/*render the object with alpha replaced with 1-a*/
Finally, the third pass should replace the value in the alpha channel with the final value 1-((1-as)(1-ad)). To do this, you need to render the object again, with its alpha value set to 1, as follows:

/*third pass*/
glColorMask(FALSE, FALSE, FALSE, TRUE);/*enable alpha channel*/
glDepthMask(TRUE);                  /*enable Z buffer*/
glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ZERO);
/*render the object with alpha replaced with 1*/
After the third pass, the frame buffer contains the correctly blended object.

Texture Mapping

QuickDraw 3D RAVE supports several texture mapping operations, which are controlled by the flags in the kQATag_TextureOp state variable. Currently these flags are defined:

#define kQATextureOp_Modulate          (1 << 0)
#define kQATextureOp_Highlight         (1 << 1)
#define kQATextureOp_Decal             (1 << 2)
#define kQATextureOp_Shrink            (1 << 3)
Note
A complete description of texture mapping operations is provided in "Texture Operations" (page 1-51).
To support the kQATextureOp_Modulate mode on an OpenGL-compliant rasterizer, you can use the GL_MODULATE mode, where the kd_r, kd_g, and kd_b fields of a texture vertex specify the modulating color. Note, however, that GL_MODULATE does not allow these color values to be greater than 1.0, whereas QuickDraw 3D RAVE does allow them to be greater than 1.0. Values greater than 1.0 can provide improved image realism, and new hardware should support them. A more reasonable maximum modulation amplitude is 2.0.

You can support the kQATextureOp_Highlight mode by performing two rendering passes. The first pass should render the texture-mapped object (possibly also with modulation, as just described), and the second pass should add the specular highlight value.

/*first pass*/
glDepthMask(FALSE);                 /*disable Z buffer*/
/*render the texture-mapped object here*/

/*second pass*/
glDepthMask(TRUE);                  /*enable Z buffer*/
glBlendFunc(GL_ONE, GL_ONE);        /*add highlight color*/
/*render the highlight color as a Gouraud-shaded object here*/
On the second pass, you should render the highlight color, using the ks_r, ks_g, and ks_b fields of a texture vertex, as a Gouraud-shaded object.

If the kQATextureOp_Modulate flag is clear (that is, is no texture map color modulation is to be performed), you can support the kQATextureOp_Decal mode using the OpenGL GL_DECAL mode. If, in addition, the kQATextureOp_Highlight flag is set, you need to perform two rendering passes, as just described.

IMPORTANT
There is currently no known method of accurately rendering to OpenGL-compliant hardware when both the kQATextureOp_Decal and the kQATextureOp_Modulate flags are set. You should determine the best method of implementing this mode correctly on your hardware. If your hardware cannot handle both modes at once, you should ignore the kQATextureOp_Modulate mode whenever kQATextureOp_Decal is set.

Previous Book Contents Book Index Next

© Apple Computer, Inc.
28 AUG 1996